<?php
/*
	Абстракция от конкретной СУБД:
	* упрощение вызова sql-запросов
	* защита кавычками от sql-oriented violations

	DBAL( &$config )

	---------

	NB: вместо $config можно передавать любой объект, содержащий свойства $this->db_*
			и опционально $this->debug (объект класса Debug)

	Этот класс -- верхний уровень абстракции от СУБД.
	Для конкретных СУБД реализован нижний уровень -- DBAL_****, через $config->db_al="****"
	Файлы должны лежать в одном каталоге.

	---------

	// Защита строкового значения кавычками

	* Quote( $value ) -- обквочивает значение, делая его безопасным для SQL
			- возвращает что-то вроде '13' или 'строка'
				(вместе с правильными кавычками, которые вокруг добавлять уже не надо)

	// Упрощение вызовов SQL с возвратом в виде хэшей

	* Query( $sql, $limit=0, $offset=0 ) -- самый навороченный, используется просто ВСЕГДА
			- делает предобработку запроса (если пользователь -- админ, то "выключает" из запроса "active=1")
			- получает результат запроса
			- выкладывает его в массив из ассоциированных массивов (хэшей)
	* _Query( $sql, $limit=0, $offset=0 ) -- то же самое, только без предобратки
	* QueryOne( $sql ) -- то же, что Query, но берёт из БД и возвращает только первую запись хэшем
												или false, если результат -- пустота.

	* Insert( $sql ) -- запрос типа INSERT, возвращаем идентификатор вставленной записи

	// Прочее

	* RecordCount( $sql ) -- чтобы не вызывать Query для этой задачи, возвращает число
	* Close() -- лучше бы вызывать в конце, а то мало ли что мы тут "напридумывали" в плане освобождения памяти
	* _Connect() -- присоединяется к БД, получая рукоятку. Вызывается из конструктора, если не указан спецпараметр

	---------

	Типовые DBAL-параметры в $this:

	$this->db_host     = "localhost"
	$this->db_user     = "username"
	$this->db_password = "pwd"
	$this->db_name     = "project_db"
	$this->db_prefix   = "prefix_"

	$this->db_al       = "mysql" <- по этой штуке мы ловим, какой дбал нам был нужен

	---------

	Типичный приспособленный к этому дбалу запрос выглядит так ($db -- это дбаловский объект):

	$a = $db->Query( "select * from ".$db->db_prefix." where active=1 and section_id=". $db->Quote(35) );
	echo $a[0]["section_id"];

=============================================================== v.2 (kuso@npj)
 */

class RL_DBAL
{

	/**
	 * Контекст окружения
	 *
	 * @var RL_Context
	 */
	//public $ctx;

	/**
	 * Низкоуровневый DBAL
	 *
	 * @var RL_DBAL_*
	 */
	public $lowlevel;

	/*
	// NB: не реализовано в первой версии (ForR2, ?????)
	var $active_from = "active=1";        // из чего и во что превращать статус "видимости"
	var $active_to   = "active=active";   // чего-то в БД для неавторизованного пользователя
	var $active_role = "editor";          // для какой "роли" это следует делать (пока задел на будущее)
	 */

	/**
	 * Конструктор. Если надо, то создает соединение
	 *
	 * @param RL_Context $ctx
	 * @param bool $connect создавать ли соединение
	 */
	public function __construct( RL_Debug $debug )
	{
		//$this->ctx = $ctx;
		$this->debug = $ctx->debug;
	}
	function _error( $error_msg )
	{
		$error_msg = "DBAL [".$this->db_al."] Error: " . $error_msg;
		debug($error_msg);
		$this->error = $error_msg;
		if (isset($this->debug)) {
			$this->debug->error($error_msg);
		} else {
			ob_end_clean();
			error($error_msg);
		}

	}

	public function connect()
		/**
		 * Устанавливает соединение с БД
		 *
		 * @return bool
		 */
	{
		if (!isset($this->lowlevel)) {
			require_once( "RL/DBAL/" . ucfirst(strtolower($this->db_al)) . ".php" );
			$lowlevel_name = "RL_DBAL_".$this->db_al;
			$lowlevel = new $lowlevel_name( $this, $log=false );
			$this->lowlevel = $lowlevel;
		}
		$this->lowlevel->Connect();
	}
	public function close()
		/**
		 * Закрывает соединение
		 *
		 * @return void
		 */
	{
		$this->lowlevel->Close();
	}

	// стартует транзакцию
	public function begin()
	{
		return $this->lowlevel->Begin();
	}
	// закрывает транзакцию
	public function end()
	{
		return $this->lowlevel->end();
	}
	// откат
	public function rollback()
	{
		return $this->lowlevel->rollback();
	}
	
	public function isErrorInTransaction() {
		return $this->lowlevel->is_rollback();
	}
	
	public function runInTransaction ($func, $args, $success_result=true)
	/**
	 * Запускает функцию в транзакции.
	 * 
	 * @param mixed $func функция (или метод в виде array()).
	 * @param array $args список аргументов.
	 * @param mixed $success_result что считать успешным выполнением функции [true]
	 * @access public
	 * @return mixed
	 */
	{
		$this->begin();
		$status = call_user_func_array($func, $args);
		if ($status === $success_result) {
			$this->lowlevel->end();
		} else {
			$this->lowlevel->rollback();
		}
		return $status;
	} // end of function runInTransaction

	public function setDriver ($name)
	{
		$this->db_al = $name;
		unset($this->lowlevel);
	} // end of function setDriver
	public function loadConfig ($cfg)
	{
		$this->setDriver($cfg['db_al']);
		$this->db_host     = $cfg['db_host'];
		$this->db_user     = $cfg['db_user'];
		$this->db_name     = $cfg['db_name'];
		$this->db_password = $cfg['db_password'];
		$this->db_prefix   = $cfg['db_prefix'];
		$this->db_encoding = $cfg['db_encoding'];
	} // end of function loacConfig

	/**
	 * Защита строкового значения кавычками
	 *
	 * @param string $value значение
	 * @return string
	 */
	public function quote ($value)
	{
		return $this->lowlevel->Quote( $value );
	}
	public function quoteField ($value)
	{
		return $this->lowlevel->quoteField($value);
	}
	public function quoteTable ($value)
	{
		return $this->lowlevel->quoteTable($value);
	}
	public function quoteDatabase ($value)
	{
		return $this->lowlevel->quoteDatabase( $value );
	}

	// Упрощение вызовов SQL с возвратом в виде хэшей
	public function query ($sql)
	{
		$data = array();

		// плейсхолдер для префикса
		$sql = str_replace ("??", $this->db_prefix, $sql);

		if ($r = $this->lowlevel->fetch($sql))
		{
			while ($row = $this->lowlevel->fetchAssoc($r)) $data[] = $row;
			$this->lowlevel->freeResult($r);
		}
		return $data;
	}
	public function queryOne( $sql )
	{
		// #1. launch Query
		$res = $this->query( $sql, 1 );
		// #2. get 1st
		if (sizeof($res)) return $res[0];
		else              return false;
	}
	public function insert( $sql )
	{
		$res = $this->lowlevel->execute($sql);
		return $this->lowlevel->insertId();
	}
	public function execute( $sql )
	{
		$status = $this->lowlevel->execute($sql);
		return $status;
	}


	public function affectedRows()
	{
		return $this->lowlevel->affectedRows();
	}
	public function recordCount( $sql )
		/**
		 * Возврат кол-ва записей в запросе
		 *
		 * @param string $sql
		 * @return int
		 */
	{
		// ????? take a look on regexp pls.
		$sql = preg_replace( "/^select.*?((\s|\().*?(\s|\)))from/i", "select count(*) as _row_number from ", $sql );
		$res = $this->queryOne($sql);
		if ($res) {
			return $res["_row_number"];
		} else {
			return 0;
		}
	}
	public function insertId()
	{
		return $this->lowlevel->insertId();
	}

	// EOC{ DBAL }
}

?>
